package devcontainer

import (
	"archive/tar"
	"context"
	"fmt"
	"io"
	"net"
	"net/url"
	"regexp"
	"strings"

	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/models/perm"
	"code.gitea.io/gitea/models/repo"
	"code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/charset"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/typesniffer"
	"code.gitea.io/gitea/modules/util"
	gitea_context "code.gitea.io/gitea/services/context"
	"github.com/google/uuid"
)

func IsAdmin(ctx context.Context, doer *user.User, repoID int64) (bool, error) {
	if doer.IsAdmin {
		return true, nil
	}

	e := db.GetEngine(ctx)
	teamMember, err := e.Table("team_user").
		Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
		Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
		Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode = ? ",
			repoID, perm.AccessModeAdmin).
		And("team_user.uid = ?", doer.ID).Exist()
	if err != nil {
		return false, nil
	}
	if teamMember {
		return true, nil
	}
	return e.Get(&repo.Collaboration{RepoID: repoID, UserID: doer.ID, Mode: 3})
}

func GetFileContentByPath(ctx context.Context, repo *repo.Repository, path string) (string, error) {
	var err error
	cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
	if err != nil {
		return "", err
	}
	// 1. 获取默认分支名
	branchName := repo.DefaultBranch
	if len(branchName) == 0 {
		branchName = cfg.Section("repository").Key("DEFAULT_BRANCH").Value()
	}

	// 2. 打开默认分支
	gitRepoEntity, err := git.OpenRepository(ctx, repo.RepoPath())
	if err != nil {
		return "", err
	}
	defer func(gitRepoEntity *git.Repository) {
		_ = gitRepoEntity.Close()
	}(gitRepoEntity)

	// 3. 获取分支名称
	commit, err := gitRepoEntity.GetBranchCommit(branchName)
	if err != nil {
		return "", err
	}

	entry, err := commit.GetTreeEntryByPath(path)
	if err != nil {
		return "", err
	}
	// No way to edit a directory online.
	if entry.IsDir() {

		return "", fmt.Errorf("%s entry.IsDir", path)
	}

	blob := entry.Blob()
	if blob.Size() >= setting.UI.MaxDisplayFileSize {

		return "", fmt.Errorf("%s blob.Size overflow", path)
	}

	dataRc, err := blob.DataAsync()
	if err != nil {

		return "", err
	}

	defer dataRc.Close()
	buf := make([]byte, 1024)
	n, _ := util.ReadAtMost(dataRc, buf)
	buf = buf[:n]

	// Only some file types are editable online as text.
	if !typesniffer.DetectContentType(buf).IsRepresentableAsText() {

		return "", fmt.Errorf("typesniffer.IsRepresentableAsText")
	}

	d, _ := io.ReadAll(dataRc)

	buf = append(buf, d...)
	if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
		log.Error("ToUTF8: %v", err)
		return string(buf), nil
	} else {
		return content, nil
	}

}

// FileExists returns true if a file exists in the given repo branch
func FileExists(path string, repo *gitea_context.Repository) (bool, error) {
	var branch = repo.BranchName
	if branch == "" {
		branch = repo.Repository.DefaultBranch
	}
	commit, err := repo.GitRepo.GetBranchCommit(branch)
	if err != nil {
		return false, err
	}
	if _, err := commit.GetTreeEntryByPath(path); err != nil {
		return false, err
	}
	return true, nil
}
func ParseImageName(imageName string) (registry, namespace, repo, tag string) {

	// 分离仓库地址和命名空间
	parts := strings.Split(imageName, "/")
	if len(parts) == 3 {
		registry = parts[0]
		namespace = parts[1]
		repo = parts[2]
	} else if len(parts) == 2 {
		registry = parts[0]
		repo = parts[1]
	} else {
		repo = imageName
	}
	// 分离标签
	parts = strings.Split(repo, ":")
	if len(parts) > 1 {
		tag = parts[1]
		repo = parts[0]
	} else {
		tag = "latest"
	}
	if registry == "" {
		registry = "docker.io"
	}
	return registry, namespace, repo, tag
}
func getSanitizedDevcontainerName(username, repoName string) string {
	regexpNonAlphaNum := regexp.MustCompile(`[^a-zA-Z0-9]`)
	sanitizedUsername := regexpNonAlphaNum.ReplaceAllString(username, "")
	sanitizedRepoName := regexpNonAlphaNum.ReplaceAllString(repoName, "")
	if len(sanitizedUsername) > 15 {
		sanitizedUsername = strings.ToLower(sanitizedUsername[:15])
	}
	if len(sanitizedRepoName) > 31 {
		sanitizedRepoName = strings.ToLower(sanitizedRepoName[:31])
	}
	newUUID, _ := uuid.NewUUID()
	uuidStr := newUUID.String()
	uuidStr = regexpNonAlphaNum.ReplaceAllString(uuidStr, "")[:15]
	return fmt.Sprintf("%s-%s-%s", sanitizedUsername, sanitizedRepoName, uuidStr)
}
func ReplacePortOfUrl(originalURL, targetPort string) (string, error) {
	// 解析原始 URL
	parsedURL, _ := url.Parse(originalURL)

	// 获取主机名和端口号
	host, _, err := net.SplitHostPort(parsedURL.Host)
	if err != nil {
		// 如果没有端口号，则 SplitHostPort 会返回错误
		// 这种情况下，Host 就是主机名
		host = parsedURL.Host
	}

	// 重新组装 Host 和端口
	parsedURL.Host = net.JoinHostPort(host, targetPort)

	// 生成新的 URL 字符串
	newURL := parsedURL.String()
	return newURL, nil
}
func GetPortFromURL(urlStr string) (string, error) {
	parsedURL, err := url.Parse(urlStr)
	if err != nil {
		return "", fmt.Errorf("解析URL失败: %v", err)
	}

	// 获取主机名和端口号
	_, port, err := net.SplitHostPort(parsedURL.Host)
	if err != nil {
		// 如果SplitHostPort失败，说明URL中没有明确指定端口
		// 需要根据协议判断默认端口
		switch parsedURL.Scheme {
		case "http":
			return "80", nil
		case "https":
			return "443", nil
		default:
			return "", fmt.Errorf("未知协议: %s", parsedURL.Scheme)
		}
	}

	// 如果端口存在，直接返回
	return port, nil
}

// addFileToTar 将文件添加到 tar 归档
func AddFileToTar(tw *tar.Writer, filename string, content string, mode int64) error {
	hdr := &tar.Header{
		Name: filename,
		Mode: mode,
		Size: int64(len(content)),
	}
	if err := tw.WriteHeader(hdr); err != nil {
		return err
	}
	if _, err := tw.Write([]byte(content)); err != nil {
		return err
	}
	return nil
}
func buildDependencyGraph(variables map[string]string) map[string][]string {
	graph := make(map[string][]string)
	varRefRegex := regexp.MustCompile(`\$[a-zA-Z_][a-zA-Z0-9_]*\b`)

	for varName, varValue := range variables {
		graph[varName] = []string{}
		matches := varRefRegex.FindAllString(varValue, -1)
		for _, match := range matches {
			refVarName := strings.TrimPrefix(match, "$")
			if _, exists := variables[refVarName]; exists {
				graph[varName] = append(graph[varName], refVarName)
			}
		}
	}
	return graph
}

func dfsDetectCycle(node string, graph map[string][]string, visited, inStack map[string]bool, path *[]string) bool {
	visited[node] = true
	inStack[node] = true
	*path = append(*path, node)

	for _, neighbor := range graph[node] {
		if !visited[neighbor] {
			if dfsDetectCycle(neighbor, graph, visited, inStack, path) {
				return true
			}
		} else if inStack[neighbor] {
			// Found cycle, complete the cycle path
			*path = append(*path, neighbor)
			return true
		}
	}

	inStack[node] = false
	*path = (*path)[:len(*path)-1]
	return false
}
func checkEachVariable(variables map[string]string) map[string]bool {
	results := make(map[string]bool)
	graph := buildDependencyGraph(variables)

	for varName := range variables {
		visited := make(map[string]bool)
		inStack := make(map[string]bool)
		var cyclePath []string

		hasCycle := dfsDetectCycle(varName, graph, visited, inStack, &cyclePath)
		results[varName] = hasCycle

		if hasCycle {
			fmt.Printf("变量 %s 存在循环引用: %v\n", varName, cyclePath)
		}
	}

	return results
}
func ContainsAnySubstring(s string, substrList []string) bool {
	for _, substr := range substrList {
		hasSubstr, _ := regexp.MatchString(`\$`+substr+`\b`, s)
		if hasSubstr {
			return true
		}
	}
	return false
}
